#! /usr/bin/env python
# coding=utf-8
# ******************************************************************************
# Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Author: senlin
# Create: 2020-08-10
# ******************************************************************************/
#     __          __     __             ___     __      __     __
#   /     |  /  |      /     |  /     |       |    \  |      /
#  |      | /   | __  |      | /      | __    | __ /  | __  |
#  |      | \   |     |      | \           |  |       |     |
#   \ __  |  \  | __   \ __  |  \      ___ |  |       | __   \ __
"""
Check SPEC
"""
import argparse
import errno
import hashlib
import logging
import os
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import urllib
import zipfile
import lzma
import filetype

import requests
import tldextract

from autoimport.checkspec.check_license import PkgLicense
from autoimport.checkspec.spec import Spec, replace_macros
from autoimport.logmodule import logger

DEFAULT_LICENSE_DICTIONARY_PATH = "./license_translations"
DEFAULT_LICENSE_LIST_PATH = "./LicenseList.txt"
DEFAULT_KEYWORDS_PATH = "./KEYWORDS"  # keywords
DEFAULT_OK_PKGS_PATH = "ok_pkgs"
DEFAULT_LICENSE_REVIEW_PKGS_PATH = "license_review_pkgs"
CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
SIGNAL_SCENES_LICENSE_COMPARE_FILE = os.path.join(CURRENT_PATH, "license_compare_result.md")
RELEASE_KEY = "Release:"
RELEASE_REGEX = "^[1-9][0-9]*$"

UNUSED_FILE_SUFFIX = (".spec~", ".specfc", ".spec.old", ".osc")
COMPRESSED_TYPE = ("tar", "tar.xz", ".tar.gz", ".tgz", ".gz", ".zip", ".bz2")

src_file_name_list = []
keywords = []
OK_pkg_list = []
License_review_pkg_list = []
NOTOK_pkg_list = []

licenses_for_source_files = []
license_translations = {}
license_list = {}

NON_SENSITIVE_WORDS = ["redhat.com", "fedoraproject.org"]
LICENSE_FILE_TARGET = ["apache-2.0",
                       "artistic",
                       "artistic.txt",
                       "libcurllicense",
                       "gpl.txt",
                       "gpl2.txt",
                       "gplv2.txt",
                       "notice",
                       "about_bsd.txt",
                       "mit",
                       "pom.xml",
                       "meta.yml"]
LICENSE_TARGET_PAT = re.compile(
    r"^(copying)|(copyright)|(copyrights)|(licenses)|(licen[cs]e)(\.(txt|xml))?$")

log_check = logger.init("CheckSpec")  # 获取一个日志对象，这里参数 只有模块名


def main():
    """ Entry point for check_licenses."""
    add_file_output(log_check)
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--tars_path", default=None, nargs="?", required=True,
                        help="set the path of all pkgs.")

    parser.add_argument("-w", "--writespec", dest="writespec", action="store_true",
                        default=False,
                        help="Overwrite the licenses of SPEC file")

    parser.add_argument("-d", "--downloadpath", default="/home/PkgSource/", nargs="?",
                        help="The dest download or extract path of tarball"
                             " (e.g. /home/tmp_tarball default: /home/PkgSource/)")

    parser.add_argument("--specshell", default="",
                        help="call shell to change spec file")

    parser.add_argument("--specify", action="store_true", default=False,
                        help="only check the file in specified software package")

    args = parser.parse_args()

    if not load_config(args.tars_path):
        log_check.warning("Something is wrong, please check config")
        sys.exit(0)
    log_check.info("Load configuration files successfully!")
    process(args)


def load_config(dir_path):
    """
    清空之前生成的临时日志，导入关键词、license黑白名单
    """
    if dir_path:
        cur_path = os.path.dirname(os.path.abspath(__file__))
        ok_pkgs_path = os.path.join(cur_path, DEFAULT_OK_PKGS_PATH)
        if not os.path.exists(ok_pkgs_path):
            os.makedirs(ok_pkgs_path, exist_ok=True)
    else:
        log_check.warning("Something is wrong, please check parameter: -p")
        sys.exit(0)

    subprocess.run("rm -rf /var/tmp/*same_lic_tar.out", check=True, shell=True)
    log_check.info("Beginning loading configuration files:")

    if not load_key_words(keywords, DEFAULT_KEYWORDS_PATH):
        log_check.warning("load %s failed", DEFAULT_KEYWORDS_PATH)
        return False

    if not PkgLicense.load_licenses_dict(license_list, DEFAULT_LICENSE_LIST_PATH):
        log_check.warning("load %s failed", DEFAULT_LICENSE_LIST_PATH)
        return False

    if not PkgLicense.load_licenses_dict(license_translations,
                                         DEFAULT_LICENSE_DICTIONARY_PATH):
        log_check.warning("load %s failed", DEFAULT_LICENSE_DICTIONARY_PATH)
        return False
    return True


def load_key_words(words, filename):
    """
    从关键词文件中导入关键词至keywords全局变量
    :return: num of keywords
    """
    conf_dir = os.path.dirname(os.path.abspath(__file__))
    conf_path = os.path.join(conf_dir, filename)
    if not os.path.isfile(conf_path):
        log_check.warning("not found the config file: %s", conf_path)
        return False
    with open(conf_path, "r") as conf_file:
        for line in conf_file:
            words.append(line.strip())
    return True


def process(args):
    """ Preparation and processing of the main entrance."""
    truncate_file("NOTOK_pkg_list.txt")
    truncate_file("OK_pkg_list.txt")
    logger.init_license_compare_file(SIGNAL_SCENES_LICENSE_COMPARE_FILE)

    if args.specify:
        process_specified_pkg(args.tars_path, args.specshell, SIGNAL_SCENES_LICENSE_COMPARE_FILE, args.downloadpath)
    else:
        process_all_pkgs(args)
    record_pkg_list()
    process_log(log_check.log_name)
    log_check.info("process all of packages end!")


def process_specified_pkg(tars_path, spec_shell, license_compare_file, download_path="/home/PkgSource/"):
    """
    Process inspection actions for specified packages.
    """
    rpm_name = tars_path.split("/")[-1]
    if len(log_check.handlers) == 0:
        logger.add_file_handler(log_check, rpm_name, "check_spec")
    pkg_path = os.path.abspath(tars_path)
    package_name = os.path.basename(pkg_path)  # 获取路径的最后一层目录
    res_flag = False
    license_flag = False
    if not os.path.isdir(tars_path):
        log_check.error('Illegal path, is not a directory: %s', pkg_path)
        return res_flag, license_flag
    if not package_name.startswith('.'):
        res_flag, license_flag = process_pkg(pkg_path, download_path, license_compare_file, spec_shell)
        if res_flag:
            current_path = os.path.dirname(os.path.abspath(__file__))  # return current path
            new_path = ""
            if license_flag:
                OK_pkg_list.append(package_name)
                new_path = os.path.join(current_path, DEFAULT_OK_PKGS_PATH)
                log_check.info('End process package: %s, result is OK!', package_name)
            else:
                License_review_pkg_list.append(package_name)
                new_path = os.path.join(current_path, DEFAULT_LICENSE_REVIEW_PKGS_PATH)
                log_check.warning('End process package: %s, licenses need review!', package_name)
            _ = not os.path.exists(new_path) and os.mkdir(new_path)
            copy_files(pkg_path, new_path)
        else:
            NOTOK_pkg_list.append(package_name)
            log_check.error('End process package: %s, result is NOT OK!', package_name)
    return res_flag, license_flag


def process_all_pkgs(args):
    """
    Process inspection actions for all packages.
    """
    args.tars_path = os.path.abspath(args.tars_path)
    with os.scandir(args.tars_path) as dir_iter:
        for entry in dir_iter:
            if not entry.name.startswith('.'):
                pkg_path = os.path.join(args.tars_path, entry.name)
                res_flag, license_flag = process_pkg(pkg_path, args.downloadpath, SIGNAL_SCENES_LICENSE_COMPARE_FILE,
                                                     args.specshell)
                if res_flag:
                    current_path = os.path.dirname(os.path.abspath(__file__))
                    new_path = ""
                    if license_flag:
                        OK_pkg_list.append(entry.name)
                        new_path = os.path.join(current_path, DEFAULT_OK_PKGS_PATH)
                        log_check.info('End process package: %s, result is OK!', entry.name)
                    else:
                        License_review_pkg_list.append(entry.name)
                        new_path = os.path.join(current_path, DEFAULT_LICENSE_REVIEW_PKGS_PATH)
                        log_check.warning('End process package: %s, licenses need review!',
                                          entry.name)
                    _ = not os.path.exists(new_path) and os.mkdir(new_path)
                    shutil.move(pkg_path, new_path)
                else:
                    NOTOK_pkg_list.append(entry.name)
                    log_check.error('End process package: %s, result is NOT OK!', entry.name)


def extract_all_pkg(pkg_path, tarballs, extract_path):
    """
    Extract all tarballs
    """
    extr_res = True
    for tname in tarballs:
        tmp_file = os.path.join(pkg_path, tname)
        if not extract_tarball(tmp_file, extract_path):
            extr_res = False
    return extr_res


def check_license(pkg_path, spec_file, compressed_files, download_path, license_compare_file, pkg_name):
    """
    Check License
    """
    license_flag = True
    licenses_for_spec = PkgLicense.scan_licenses_in_spec(spec_file, license_translations)
    log_check.info("all licenses from SPEC are: %s", licenses_for_spec)
    if not PkgLicense.check_license_safe(licenses_for_spec, license_list):
        license_flag = False
    temp_extract_path = os.path.abspath(tempfile.mkdtemp(dir=download_path))
    if not extract_all_pkg(pkg_path, compressed_files, temp_extract_path):
        log_check.error("extract source tarball failed")
        return False
    licenses_for_source_files.extend(analyse_licenses_in_compressed_file(temp_extract_path))

    log_check.info("all licenses from (%s) are: %s", pkg_path, licenses_for_source_files)
    shutil.rmtree(temp_extract_path)

    if not PkgLicense.check_license_safe(licenses_for_source_files, license_list):
        license_flag = False
    if PkgLicense.check_licenses_is_same(licenses_for_spec, licenses_for_source_files):
        log_check.info("licenses from source files are same as form SPEC:"
                       "%s == %s", licenses_for_source_files, licenses_for_spec)
        logger.record_license_compare_result(license_compare_file, pkg_name, str(licenses_for_spec),
                                             str(licenses_for_source_files), "Y")
    else:
        license_flag = False
        log_check.warning("licenses from source files are not same as form SPEC:"
                          "%s <=> %s", licenses_for_source_files, licenses_for_spec)
        logger.record_license_compare_result(license_compare_file, pkg_name, str(licenses_for_spec),
                                             str(licenses_for_source_files), "N")

    licenses_for_source_files.clear()
    licenses_for_spec.clear()  # 每一轮解析完当前组件的license之后，清空数组
    return license_flag


def check_release(spec_file):
    """
    Args:
        spec_file: spec文件路径

    Returns: 是否检查成功
    """
    check_success = False
    try:
        with open(spec_file, "r") as file:
            contents = file.readlines()
            for line in contents:
                line = line.strip("\n")
                if RELEASE_KEY in line:
                    release_num = line.replace(" ", "").split(":")[1].strip()
                    match_result = re.match(RELEASE_REGEX, release_num)
                    if match_result:
                        log_check.info("Release is %s ,PASS!", release_num)
                        check_success = True
                    else:
                        log_check.error("Release '%s' is illegal", release_num)
    except FileNotFoundError:
        log_check.error("spec file is not exist")
    return check_success


def process_pkg(pkg_path, download_path, license_compare_file, spec_shell=""):
    """
    Process inspection actions for package.
    """
    pkg_name = os.path.basename(pkg_path)
    log_check.info('****************Start process package: %s *******************', pkg_name)
    res_flag = True
    spec_file = ""
    compressed_files = []

    if spec_shell:
        change_spec_by_shell(spec_shell, pkg_path)  # 在check spec格式前调用脚本修改spec

    for file_name in os.listdir(pkg_path):
        file_path = os.path.join(pkg_path, file_name)
        log_check.info('Scanning file: %s', file_path)
        if is_unused_spec_file(file_name):
            log_check.info('%s is unused', file_path)
            continue
        if is_compressed_file(file_path):  # 记录哪些是压缩包文件以备解析license
            log_check.info('%s is compressed and wait for checking licenses', file_path)
            compressed_files.append(file_path)
            continue
        if file_name.endswith(".spec"):
            log_check.info('%s is spec and start checking:', file_path)
            spec_file = file_path
            inputspec = Spec.from_file(file_path)  # 获取spec初步解析之后的对象
            res_flag = check_url(download_path, inputspec, pkg_path)

        line_list = find_keyword_in_file(file_path, file_name, False)
        if line_list:
            for line_info in line_list:
                # 修改日志输出级别
                log_check.error("[%s:%d] %s | keywords: %s",
                                file_name,
                                line_info["line_no"],
                                line_info["line"],
                                str(line_info["keywords"]))
            res_flag = False
        else:
            log_check.info('Check keywords in: %s, PASS!', file_name)
        # Check Release
        if not check_release(spec_file):
            res_flag = False
    # 若spec文件不存在，直接报错退出
    if not os.path.exists(spec_file):
        return False, False
    # Check License
    license_flag = check_license(pkg_path, spec_file, compressed_files, download_path, license_compare_file, pkg_name)
    return res_flag, license_flag


def check_url(download_path, inputspec, pkg_path):
    """
    检查url
    Args:
        download_path: 下载路径
        inputspec: spec文件
        pkg_path: 源码包

    Returns: 检查结果
    """
    res_flag = True
    if not check_url_of_pkg(inputspec):  # 校验URL字段的有效性
        res_flag = False
    if not check_url_of_source(pkg_path, inputspec, download_path):  # 校验source的链接的有效性及md5值
        res_flag = False
    if not check_url_source_consistency(inputspec):
        res_flag = False
    return res_flag


def is_compressed_file(src_file):
    """
    Determine whether it is a compressed file.
    """
    if os.path.isfile(src_file):
        if src_file.endswith(COMPRESSED_TYPE):
            return True
    return False


def analyse_licenses_in_compressed_file(extract_path):
    """
    Begin process licenses in source file.
    """
    return PkgLicense.scan_licenses_in_source(extract_path, license_translations)


def is_include_non_sensitive_word(line_info):
    """
        全词匹配：判断输入的字符串中是否有关键字
    """
    for word in NON_SENSITIVE_WORDS:
        # 先用find函数过滤
        if -1 != line_info.find(word):
            # 匹配下列情况：
            # ^word$;
            # ^word+任意非字母数字下划线;
            # 任意非字母数字下划线)+word$;
            # 任意非字母数字下划线+word+任意非字母数字下划线
            pattern_str = r'(^{word}$)|(^{word}(\W+).*)|(.*(\W+){word}$)|(.*(\W+){word}(\W+).*)' \
                .format(word=word)
            result = re.match(r'' + pattern_str + '', line_info)
            if result:
                return True
    return False


def find_keyword_in_file(filepath, filename, save_code=False):
    """
    返回文件中所有包含keywords的行号、代码，数据结构如下
    [
        {
            line_no: xxx,
            line: xxx,
            keywords: ['a', 'b']
        },
    ]
    :return: list
    """
    line_list = []

    try:
        file_handle = open(filepath, "r")
        lines = file_handle.readlines()
        line_no = 0
        for line in lines:
            line_no += 1
            keyword_set = []
            for key in keywords:
                if key in line and not is_include_non_sensitive_word(line):
                    keyword_set.append(key)
            if len(keyword_set) > 0:
                line_info = {}
                line_info["keywords"] = keyword_set
                line_info["line_no"] = line_no
                line_info["line"] = line.strip() if save_code else ""
                line_list.append(line_info)
    except LookupError:  # 获取异常类型（）
        # 异常发生的时候不会崩溃，而是执行这个里面的代码
        log_check.warning("%s can not be opened", filename)
        line_list = []
    except FileNotFoundError:
        log_check.error("%s is not found", filename)
        line_list = []
    return line_list


def check_url_of_source(pkg_path, inputspec, download_path):
    """
    Check the validity of the download link pointing to
    the source file and compare the md5 value after downloading.
    """
    valid = True
    if not inputspec.name:
        log_check.error("name not exist in spec")
        return False
    download_path = os.path.join(download_path, inputspec.name)  # 指定下载文件存放的路径
    for source_index, source_url in inputspec.sources_dict.items():
        source_url = source_url.strip('\n')
        source_url = replace_macros(source_url, inputspec)  # 替换网址中可能存在的宏
        log_check.info("%s: %s", source_index, source_url)

        if not os.path.exists(download_path):
            log_check.warning("Selected folder not exist, try to create it.")
            os.makedirs(download_path)

        log_check.info("Try downloading: %s", source_index)
        source_file_name = source_url.split('/')[-1]
        download_source_file = os.path.join(download_path, source_file_name)
        origin_source_file = os.path.join(pkg_path, source_file_name)  # 指定本地已有的source文件
        if os.path.exists(download_source_file):
            valid = check_md5(origin_source_file, download_source_file, source_index)
        else:
            try:
                urllib.request.urlretrieve(source_url, filename=download_source_file)  # 下载source文件
                valid = check_md5(origin_source_file, download_source_file, source_index)
            except Exception as exp:
                log_check.error("retrieve %s error.\n Exception: %s", source_url, str(exp))
                if source_index == "Source" or source_index == "Source0":
                    valid = False

        if valid:
            log_check.info("check url and md5 of %s PASS!", source_index)
        else:
            log_check.error("check url and md5 of %s Failed!", source_index)

    return valid


def check_url_source_consistency(inputspec):
    """
    检查spec中url和source0顶级域名的一致性
    """
    if not inputspec.url:
        return True
    url_domain = tldextract.extract(inputspec.url).domain
    src_url = inputspec.sources_dict.get("Source0", "")
    src_url = inputspec.sources_dict.get("Source", "") if not src_url else src_url
    src_url = replace_macros(src_url, inputspec)
    src_domain = tldextract.extract(src_url).domain
    if url_domain == src_domain:
        log_check.info("check domain of url and source0 PASS!")
        return True
    log_check.warning("url domain(%s) is different from source0 domain(%s)",
                      url_domain, src_domain)
    return False


def check_url_of_pkg(inputspec):
    """
    Check the validity of the URL.
    """
    valid = True
    try:
        homepage = inputspec.url
        if not homepage:
            log_check.error("url not exist")
            return False
        homepage = replace_macros(inputspec.url, inputspec)
        log_check.info('homepage: %s', homepage)
        status = get_http_status_code(homepage.strip('\n'), 30)
        if status != 200:
            log_check.error('Check URL status is: %s', status)
            valid = False
    except Exception:
        log_check.error("error: %s", sys.exc_info()[0])
        valid = False
    return valid


def check_md5(origin_source_file, download_source_file, source_index):
    """
    Check the md5 of two source files.
    """
    _ = source_index
    md5_local = get_md5(origin_source_file)
    md5_remote = get_md5(download_source_file)
    if md5_local != md5_remote:
        return False
    return True


def get_md5(file):
    """
    Get the md5 of given file.
    """
    md5 = hashlib.md5()
    if not os.path.exists(file):
        log_check.error("%s file not exist", os.path.basename(file))
        return None
    file_handle = open(file, 'rb')
    while True:
        block = file_handle.read(8096)
        if not block:
            break
        md5.update(block)
    file_handle.close()
    return md5.hexdigest()


def truncate_file(file):
    """
    Truncate the given file.
    """
    if not os.path.exists(file):
        os.system(r'touch %s' % file)
    with open(file, 'r+') as file_handle:
        file_handle.truncate()


def get_http_status_code(url, timeout=30):
    """
    Get the http status code of given url.
    """
    valid_http = url.startswith('http:')
    valid_https = url.startswith('https:')
    status_code = 404

    if valid_http or valid_https:
        try:
            request = requests.get(url, timeout=timeout)
            status_code = request.status_code
        except Exception as ex:
            template = "An exception of type {0} occurred. Arguments:\n{1!r}"
            message = template.format(type(ex).__name__, ex.args)
            log_check.error(message)
    return status_code


def get_contents(filename):
    """
    Get file contents and return values. If read failed return None.
    """
    with open(filename, "rb") as file_handle:
        return file_handle.read()
    return None


def _extract_tar(tarball_path, extract_path):
    """
    Extract tar package in extract_path. If extract failed the program will exit.
    """
    res_flag = True
    if not os.path.isfile(tarball_path):
        log_check.error("%s is not a file", tarball_path)
        res_flag = False
        return res_flag

    try:
        with tarfile.open(tarball_path) as content:
            content.extractall(path=extract_path)
    except FileNotFoundError:
        res_flag = False
        log_check.error("%s can not be found", tarball_path)
    except tarfile.CompressionError:
        res_flag = False
        log_check.error("%s can not be decoded correctly", tarball_path)
    except tarfile.ReadError:
        res_flag = False
        log_check.error("%s is invalid tar file", tarball_path)
    return res_flag


def _extract_bz2(bz2_path, extract_path):
    """
    Extract bz2 package in extract_path. If extract failed the program will exit.
    """
    res_flag = True
    if not os.path.isfile(bz2_path):
        log_check.error("%s is not a bz2 file", bz2_path)
        res_flag = False
        return res_flag

    try:
        archive = tarfile.open(bz2_path, 'r:bz2')
        for tarinfo in archive:
            archive.extract(tarinfo, extract_path)
        archive.close()
    except FileNotFoundError:
        res_flag = False
        log_check.error("%s can not be found", bz2_path)
    except tarfile.CompressionError:
        res_flag = False
        log_check.error("%s can not be decoded correctly", bz2_path)
    except tarfile.ReadError:
        res_flag = False
        log_check.error("%s is invalid tar file", bz2_path)
    return res_flag


def _extract_zip(zip_path, extract_path):
    """
    Extract zip package in extract_path. If extract failed the program will exit.
    """
    res_flag = True
    if not os.path.isfile(zip_path):
        log_check.error("%s is not a zip file", zip_path)
        res_flag = False
        return res_flag

    try:
        zip_file = zipfile.ZipFile(zip_path)
        zip_file.extractall(extract_path)
        zip_file.close()
    except FileNotFoundError:
        res_flag = False
        log_check.error("%s can not be found", zip_path)
    except zipfile.BadZipfile:
        res_flag = False
        log_check.error("%s is bad zip file", zip_path)
    except zipfile.LargeZipFile:
        res_flag = False
        log_check.error("The zip file requires the zip64 feature but is not enabled")
    return res_flag


def _extract_xz(xz_path, extract_path):
    """
    extract tar.xz to specific path
    """
    if not os.path.isfile(xz_path):
        log_check.error("%s is not a file", xz_path)
        return False
    tar_file = os.path.join(extract_path, os.path.basename(xz_path).split(".xz")[0])
    try:
        with lzma.open(xz_path, "rb") as file_input:
            with open(tar_file, "wb") as output:
                shutil.copyfileobj(file_input, output)
                res = _extract_tar(tar_file, extract_path)
                os.remove(tar_file)
                return res
    except FileNotFoundError:
        log_check.error("%s can not found", xz_path)
        return False
    except lzma.LZMAError:
        log_check.error("%s can not be decompressed", xz_path)
        return False


def extract_tarball(tb_file, extract_path):
    """
    Entrypoint for extract tarball
    """
    method_map = {
        "tar": _extract_tar,
        "gz": _extract_tar,
        "bz2": _extract_bz2,
        "zip": _extract_zip,
        "xz": _extract_xz
    }
    file_type = filetype.guess(tb_file)
    method = method_map.get(file_type.extension, None)
    if method:
        return method(tb_file, extract_path)
    log_check.error("filetype: %s not support to extract")
    return False


def _extract_xz(xz_path, extract_path):
    """
    extract tar.xz to specific path
    """
    if not os.path.isfile(xz_path):
        log_check.error("%s is not a file", xz_path)
        return False
    tar_file = os.path.join(extract_path, os.path.basename(xz_path).split(".xz")[0])
    try:
        with lzma.open(xz_path, "rb") as file_input:
            with open(tar_file, "wb") as output:
                shutil.copyfileobj(file_input, output)
                res = _extract_tar(tar_file, extract_path)
                os.remove(tar_file)
                return res
    except FileNotFoundError:
        log_check.error("%s can not found", xz_path)
        return False
    except lzma.LZMAError:
        log_check.error("%s can not be decompressed", xz_path)
        return False


def extract_tarball(tb_file, extract_path):
    """
    Entrypoint for extract tarball
    """
    method_map = {
            "tar": _extract_tar,
            "gz": _extract_tar,
            "bz2": _extract_bz2,
            "zip": _extract_zip,
            "xz": _extract_xz
            }
    ft = filetype.guess(tb_file)
    method = method_map.get(ft.extension, None)
    if method:
        return method(tb_file, extract_path)
    log_check.error("filetype: %s not support to extract")
    return False


def decode_license(license_string, charset):
    """
    Decode the license string. return the license string or nothing.
    """
    if not charset:
        return None
    return license_string.decode(charset)


def overwrite_spec(specfile):
    """
    Write License in SPEC file.
    If open file failed, return nothing.
    """
    licenses_for_wirte = "License:\t"
    for lic in licenses_for_source_files:
        if lic == licenses_for_source_files[0]:
            licenses_for_wirte += lic
        else:
            licenses_for_wirte += " and " + lic
    licenses_for_wirte += "\n"

    try:
        with open(specfile, 'r') as specfd:
            lines = specfd.readlines()
            specfd.close()
    except FileNotFoundError:
        return
    file_handle = open(specfile, 'w')
    for line in lines:
        if line.startswith("License"):
            file_handle.write(licenses_for_wirte)
        else:
            file_handle.write(line)
    file_handle.close()
    log_check.info("licenses wirte to spec success")


def change_spec_by_shell(shell, filepath):
    """
    Format the given spec file.
    """
    # bash path/spec_prep_plus.sh path/package_name/*.spec
    cmd = "bash " + shell + " " + filepath + "/*.spec"
    try:
        proc_new = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
    except OSError as err:
        if err.errno == errno.ENOENT:
            log_check.error("Command %s not found.", cmd)
        return None, None
    stdout_new, _ = proc_new.communicate()
    returncode = proc_new.returncode
    proc_new.poll()
    return stdout_new.decode('utf-8', 'ignore'), returncode


def process_log(log_file):
    """
    Extract clear error information from the full log.
    """
    with open(log_file, 'r') as file_to_read:
        with open('packages_err_log.txt', 'w') as err_log:
            for line in file_to_read.readlines():
                if ("Start process package" in line or "WARNING" in line or "ERROR" in line):
                    if 'exc_type' in line:
                        err_log.write(pre_line)
                    err_log.write(line)
                pre_line = line


def record_pkg_list():
    """
    Extract the list of software packages that passed
    and failed inspections according to the logs.
    """
    NOTOK_pkg_list.sort()
    License_review_pkg_list.sort()
    OK_pkg_list.sort()

    with open("NOTOK_pkg_list.txt", 'a') as file_handle:
        file_handle.write("\n".join(NOTOK_pkg_list))

    with open("License_review_pkg_list.txt", 'a') as file_handle:
        file_handle.write("\n".join(License_review_pkg_list))

    with open("OK_pkg_list.txt", 'a') as file_handle:
        file_handle.write("\n".join(OK_pkg_list))


def is_spec_file(filename):
    """
    根据后缀名判断 是否为spec文件
    :return bool
    """
    return filename.endswith(".spec")


def is_unused_spec_file(filename):
    """
    根据后缀名判断 非软件包必须文件
    :return bool
    """
    return filename.endswith(UNUSED_FILE_SUFFIX)


def copy_files(srcpath, targetpath):
    """
    Copy files
    """
    if os.path.exists(srcpath):
        for file in os.listdir(srcpath):
            file_path = os.path.join(srcpath, file)
            if not os.path.exists(targetpath):
                os.mkdir(targetpath)
            if os.path.isfile(file_path):
                shutil.copy(file_path, targetpath)


def add_file_output(log):
    """

    Args:
        log: 日志对象

    Returns: 添加文件输出流的日志对象

    """
    log.log_name = "check_result.log"
    log_file_name = os.path.join(CURRENT_PATH, "check_result.log")
    with open(log_file_name, "w+") as file:
        file.truncate()
    file_handler = logging.FileHandler(log_file_name)
    file_handler.setLevel(logger.LEVEL)
    file_handler.setFormatter(logger.FORMATTER)
    log.handlers = []
    log.addHandler(file_handler)


if __name__ == '__main__':
    main()
